package query

import (
	"context"
	"fmt"
	"path/filepath"
	"strings"
	"time"

	"github.com/glennliao/apijson-go/config"
	"github.com/glennliao/apijson-go/consts"
	"github.com/glennliao/apijson-go/model"
	"github.com/glennliao/apijson-go/util"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/samber/lo"
)

const (
	J节点类型结构 = iota // 结构节点
	J节点类型查询        // 查询节点
	J节点类型引用        // 引用节点
	J节点类型函数        // functions 节点
)

type J节点处理器 interface {
	J解析()
	H获取数据()
	C处理结果()
	J节点类型() int
}

type F分页 struct {
	T条数 int
	Y页码 int
}

type J节点 struct {
	S上下文   context.Context
	C查询上下文 *C查询结构体

	// 当前节点key Todos, 如果Todo[], 保存为Todo
	J键 string
	// 当前节点path -> []/Todos
	L路径 string
	// 节点类型
	L类型 int

	// 字段映射
	Z字段映射 map[string]string

	// 是否为列表节点
	S是列表 bool

	F分页参数 *F分页 // 分页参数

	// 访问当前节点的角色
	J角色 string

	// 节点的请求数据
	Q请求数据  model.Map
	J简单请求值 string // 非对象结构

	// 节点数据执行器
	Z执行器 C查询执行器接口

	K开始时间 time.Time
	J结束时间 time.Time

	// 执行完毕
	W完成 bool

	Y延迟执行 bool // 后续执行

	F返回值 any
	C错误  error

	Z子节点 map[string]*J节点

	Y引用键映射 map[string]J节点引用 // 关联字段

	Z主表键 string // 主查询表

	Z总数   int64 // 数据总条数
	X需要总数 bool

	C处理器 J节点处理器

	Z执行器配置 *config.Z执行器配置
}

// 节点引用 节点依赖引用
type J节点引用 struct {
	Z字段 string
	J节点 *J节点
}

/**
节点 生命周期
新建 -> 构建子节点 -> 解析 -> 获取数据 -> 处理结果
*/

func X新建节点(查询 *C查询结构体, 键 string, 路径 string, 节点请求 any) *J节点 {

	if 查询.D打印过程日志 {
		g.Log().Debugf(查询.S上下文, "【节点】(%s) <新建> ", 路径)
	}

	J节点实例 := &J节点{
		S上下文:   查询.S上下文,
		C查询上下文: 查询,
		J键:     键,
		L路径:    路径,
		K开始时间:  time.Now(),
		W完成:    false,
	}

	J节点实例.J键, J节点实例.S是列表 = J解析节点键(键, 路径)

	// 节点类型判断
	if util.S是否首字母大写(J节点实例.J键) { // 大写开头, 为查询节点(对应数据库)
		J节点实例.L类型 = J节点类型查询
	} else if strings.HasSuffix(J节点实例.J键, consts.Y引用键后缀) {
		J节点实例.L类型 = J节点类型引用
	} else if strings.HasSuffix(J节点实例.J键, consts.H函数键后缀) {
		J节点实例.L类型 = J节点类型函数
	} else {
		J节点实例.L类型 = J节点类型结构 // 结构节点下应该必须存在查询节点

		if 查询.B不验证访问 == false {
			if lo.Contains(查询.S数据库元数据.H获取表名称列表(), J节点实例.J键) {
				J节点实例.L类型 = J节点类型查询
			}
		}
	}

	switch J节点实例.L类型 {
	case J节点类型查询:
		J节点实例.C处理器 = X新建查询节点(J节点实例)
	case J节点类型引用:
		J节点实例.C处理器 = X新建引用节点(J节点实例)
	case J节点类型结构:
		J节点实例.C处理器 = X新建结构节点(J节点实例)
	case J节点类型函数:
		J节点实例.C处理器 = X新建函数节点(J节点实例)
	}

	switch 节点请求.(type) {
	case map[string]any:
		J节点实例.Q请求数据 = 节点请求.(map[string]any)
	case model.Map:
		J节点实例.Q请求数据 = 节点请求.(model.Map)
	default:
		J节点实例.J简单请求值 = gconv.String(节点请求)
	}

	J节点实例.Z字段映射 = map[string]string{}

	return J节点实例
}

func J解析节点键(输入键 string, 路径 string) (键 string, 是列表 bool) {
	键 = 输入键
	if strings.HasSuffix(键, consts.L列表键后缀) {
		是列表 = true
		键 = 键[0 : len(键)-len(consts.L列表键后缀)]
	} else {
		if strings.HasSuffix(filepath.Dir(路径), consts.L列表键后缀) { // 父节点是 []
			是列表 = true
		}
	}

	return
}

func (n *J节点) G构建子节点() error {

	if n.L类型 == J节点类型查询 && !util.C存在首字母大写的键(n.Q请求数据) { // 查询节点嵌套查询节点, 目前不支持
		return nil
	}

	// 最大深度检查
	最大深度 := n.C查询上下文.C查询配置.H获取最大树深度()
	if len(strings.Split(n.L路径, "/")) > 最大深度 {
		return consts.X新建请求验证错误(fmt.Sprintf("深度(%s) > %d", n.L路径, 最大深度))
	}

	子节点列表 := make(map[string]*J节点)

	for 键, 值 := range n.Q请求数据 {

		if strings.HasPrefix(键, consts.Y引用键后缀) {
			continue
		}

		if n.L类型 == J节点类型查询 && !util.S是否首字母大写(键) { // 查询节点嵌套查询节点, 目前不支持
			continue
		}

		if n.S是列表 {
			if lo.Contains([]string{consts.Z总数, consts.Y页码}, 键) {
				continue
			}
		}

		节点路径 := n.L路径
		if 节点路径 != "" { // 根节点时不带/
			节点路径 += "/"
		}
		子节点 := X新建节点(n.C查询上下文, 键, 节点路径+键, 值)

		if n.L类型 != J节点类型查询 { // 非查询节点role主要的功能是传递角色(设置该节点下子节点的角色)
			S设置节点角色(子节点, "", n.J角色)
		}

		错误 := 子节点.G构建子节点()
		if 错误 != nil {
			return 错误
		}
		子节点列表[键] = 子节点
	}

	if len(子节点列表) > 0 {

		// 最大宽度检查, 为当前节点的子节点数
		最大宽度 := n.C查询上下文.C查询配置.H获取最大树宽度()
		if len(子节点列表) > 最大宽度 {
			路径 := n.L路径
			if 路径 == "" {
				路径 = "root"
			}
			return consts.X新建请求验证错误(fmt.Sprintf("宽度(%s) > %d", 路径, 最大宽度))
		}

		n.Z子节点 = 子节点列表

		for _, 节点 := range 子节点列表 {
			n.C查询上下文.L路径节点映射[节点.L路径] = 节点
		}
	}

	return nil
}

func (n *J节点) J解析() {

	if n.C查询上下文.D打印过程日志 {
		g.Log().Debugf(n.S上下文, "【节点】(%s) <解析> ", n.L路径)
	}

	if n.S是列表 {
		分页 := &F分页{}
		if 值, 存在 := n.Q请求数据[consts.Y页码]; 存在 {
			分页.Y页码 = gconv.Int(值)
		}
		if 值, 存在 := n.Q请求数据[consts.S数量]; 存在 {
			分页.T条数 = gconv.Int(值)
		}
		if 值, 存在 := n.Q请求数据[consts.C查询]; 存在 {
			switch gconv.String(值) {
			case "1", "2":
				n.X需要总数 = true
			}
		}
		n.F分页参数 = 分页
	}

	n.C处理器.J解析()

	if n.C查询上下文.D打印过程日志 {
		g.Log().Debugf(n.S上下文, "【节点】(%s) <解析结束> ", n.L路径)
	}
}

func (n *J节点) H获取数据() {

	defer func() {
		n.W完成 = true
		n.J结束时间 = time.Now()
		if n.C查询上下文.D打印过程日志 {
			g.Log().Debugf(n.S上下文, "【节点】(%s) <获取数据结束> ", n.L路径)
		}
	}()

	if n.C查询上下文.D打印过程日志 {
		g.Log().Debugf(n.S上下文, "【节点】(%s) <获取数据> 已完成: 【%v】", n.L路径, n.W完成)
	}

	if n.W完成 {
		g.Log().Error(n.S上下文, "再次执行", n.L路径)
		return
	}

	if n.C错误 != nil {
		return
	}

	n.C处理器.H获取数据()
}

func (n *J节点) J结果() (any, error) {
	if n.C错误 != nil {
		return nil, n.C错误
	}

	n.C处理器.C处理结果()

	return n.F返回值, n.C错误
}
